﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
using System.Security.Cryptography;
using System.Security.Policy;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
using Mvc.Mailer;
using viBook.Common;

namespace viBook.Models
{
    public class UserRepository : IUserRepository
    {
        private const string LocalUrl = "";
        readonly viBookContext context = new viBookContext();
        public IQueryable<User> All
        {
            get { return context.Users; }
        }

        public IQueryable<User> AllIncluding(params Expression<Func<User, object>>[] includeProperties)
        {
            IQueryable<User> query = context.Users;
            foreach (var includeProperty in includeProperties)
            {
                query = query.Include(includeProperty);
            }
            return query;
        }

        public User Find(int id)
        {
            var user = context.Users.Find(id);
            if (user == null) return null;
            if (context.Books != null)
            {
                user.UploadedBooks = context.Books.Where(b => b.UploadedBy == id);
                if (context.Chapters != null)
                {
                    foreach (var book in user.UploadedBooks)
                    {
//                        var closureBook = book;
//                        if (book.HasChapters)
//                        {
//                            book.NumberOfChapters = context.Chapters.Count(c => c.BookId == closureBook.Id);
//                        }
//                        else
//                        {
//                            var chapter = context.Chapters.FirstOrDefault();
//                            book.NumberOfPage = chapter == null ? 0 : chapter.NumberOfPages;
//                        }
                    }
                }
            }
            user.UploadedBooks = user.UploadedBooks ?? new List<Book>();
            return user;
        }

        public User FindByEmail(string email)
        {
            foreach (var user in All)
            {
                if (email.Equals(user.Email))
                {
                    return user;
                }
            }
            return null;
        }

        public User FindByUsername(string username)
        {
            foreach (var user in All)
            {
                if (username.Equals(user.Username))
                {
                    return user;
                }
            }
            return null;
        }

        public void InsertOrUpdate(User user)
        {
            if (user.Id == default(int))
            {
                context.Users.Add(user);
            }
            else
            {
                context.Entry(user).State = EntityState.Modified;
            }
        }

        public void Delete(int id)
        {
            var user = context.Users.Find(id);
            context.Users.Remove(user);
        }

        public void Save()
        {
            context.SaveChanges();
        }

        public bool ValidateUser(string username, string password, out User foundUser)
        {
            if (String.IsNullOrEmpty(username)) throw new ArgumentException("Value cannot be null or empty.", "username");
            if (String.IsNullOrEmpty(password)) throw new ArgumentException("Value cannot be null or empty.", "password");
            var users = All;
            foundUser = Enumerable.FirstOrDefault(users, user => user != null && user.Username == username && user.Password == StringToGuid(password).ToString());
            return foundUser != null;
        }

        public User CreateInternalUser(string username, string password, string email)
        {
            if (String.IsNullOrEmpty(username)) throw new ArgumentException("Value cannot be null or empty.", "username");
            if (String.IsNullOrEmpty(password)) throw new ArgumentException("Value cannot be null or empty.", "password");
            if (String.IsNullOrEmpty(email)) throw new ArgumentException("Value cannot be null or empty.", "email");
            if (Enumerable.Any(All, usr => username.Equals(usr.Username)))
            {
                throw new ArgumentException("Username exists.", "username");
            }
            if (Enumerable.Any(All, usr => email.Equals(usr.Email)))
            {
                throw new ArgumentException("Email exists.", "email");
            }

            var user = new User
                           {
                               Username = username,
                               Password = StringToGuid(password).ToString(),
                               Email = email,
                               Status = Constant.StatusCreated,
                               IsExternal = false,
                               JoinedDate = DateTime.Now,
                               LastJoined = DateTime.Now,
                               ValidationCode = GetNewValidationCode()
                           };

            InsertOrUpdate(user);
            Save();
            return user;
        }

        public User CreateExternalUser(string email)
        {
            if (String.IsNullOrEmpty(email) || Enumerable.Any(All, usr => email.Equals(usr.Email)))
            {
                return null;
            }

            var user = new User
                           {
                               Email = email,
                               IsExternal = true,
                               JoinedDate = DateTime.Now,
                               LastJoined = DateTime.Now
                           };
            InsertOrUpdate(user);
            Save();
            return user;
        }

        public Guid StringToGuid(string value)
        {
            // Create a new instance of the MD5CryptoServiceProvider object.
            MD5 md5Hasher = MD5.Create();
            // Convert the input string to a byte array and compute the hash.
            byte[] data = md5Hasher.ComputeHash(Encoding.Default.GetBytes(value));
            return new Guid(data);
        }

        public User Reactivate(string email)
        {
            var user = FindByEmail(email);
            if (!user.IsExternal && user.Status.Equals(Constant.StatusCreated))
            {
                user.ValidationCode = GetNewValidationCode();
                InsertOrUpdate(user);
                Save();
                return user;
            }
            return null;
        }

        public bool ValidateActivationCode(int userId, string activationCode)
        {
            var user = Find(userId);
            if (user.Status.Equals(Constant.StatusCreated) && activationCode.Equals(user.ValidationCode))
            {
                user.Status = Constant.StatusApproved;
                user.ValidationCode = "";

                InsertOrUpdate(user);
                Save();
                return true;
            }
            return false;
        }

        public User RequestResetPassword(string email)
        {
            var user = FindByEmail(email);
            if (user != null && !user.IsExternal && user.Status.Equals(Constant.StatusApproved))
            {
                user.ValidationCode = GetNewValidationCode();
                InsertOrUpdate(user);
                Save();
                return user;
            }
            return null;
        }

        public bool ValidateNewPassword(int userId, string validationCode, string newPassword)
        {
            var user = Find(userId);
            if (validationCode.Equals(user.ValidationCode))
            {
                user.Password = StringToGuid(newPassword).ToString();
                user.ValidationCode = "";
                InsertOrUpdate(user);
                Save();
                return true;
            }
            return false;
        }

        private string GetNewValidationCode()
        {
            long i = Guid.NewGuid().ToByteArray().Aggregate<byte, long>(1, (current, b) => current * (b + 1));

            return string.Format("{0:x}", i - DateTime.Now.Ticks);
        }

        public static string RpHash(string value)
        {
            int hash = 5381;
            value = value.ToUpper();
            for (int i = 0; i < value.Length; i++)
            {
                hash = ((hash << 5) + hash) + value[i];
            }
            return hash.ToString();
        }
    }

    public interface IUserRepository
    {
        IQueryable<User> All { get; }
        IQueryable<User> AllIncluding(params Expression<Func<User, object>>[] includeProperties);
        User Find(int id);
        User FindByEmail(string email);
        User FindByUsername(string username);
        void InsertOrUpdate(User user);
        void Delete(int id);
        void Save();
        bool ValidateUser(string username, string password, out User foundUser);
        User CreateInternalUser(string username, string password, string email);
        User CreateExternalUser(string email);
        Guid StringToGuid(string value);
        User Reactivate(string email);
        bool ValidateActivationCode(int userId, string activationCode);
        User RequestResetPassword(string email);
        bool ValidateNewPassword(int userId, string validationCode, string newPassword);
    }
}